/*
Miranda IM for Windows Search

Copyright (C) 2012-2014 Nikolay Raspopov

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "stdafx.h"
#include "MirandaSearch.h"
#include "MirandaProtocol.h"
#include "MirandaPlugin.h"

CMirandaSearchModule _AtlModule;

extern "C" BOOL WINAPI DllMain(HINSTANCE /*hInstance*/, DWORD dwReason, LPVOID lpReserved)
{
	return _AtlModule.DllMain( dwReason, lpReserved ); 
}

STDAPI DllCanUnloadNow(void)
{
	return _AtlModule.DllCanUnloadNow();
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
	return _AtlModule.DllGetClassObject( rclsid, riid, ppv );
}

STDAPI DllRegisterServer(void)
{
	return _AtlModule.DllRegisterServer();
}

STDAPI DllUnregisterServer(void)
{
	return _AtlModule.DllUnregisterServer();
}

STDAPI DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
{
	HRESULT hr;
	static const wchar_t szUserSwitch[] = L"user";

	if ( pszCmdLine )
	{
		if ( _wcsnicmp( pszCmdLine, szUserSwitch, _countof( szUserSwitch ) ) == 0 )
		{
			ATL::AtlSetPerUserRegistration( true );
		}
/*#ifdef _DEBUG
		else
		{
			CComObject< CMirandaProtocol >* pMirandaProtocol;
			hr = CComObject< CMirandaProtocol >::CreateInstance( &pMirandaProtocol );
			ATLASSERT( SUCCEEDED( hr ) );

			CComPtr< ISearchProtocol > pProtocol;
			hr = pMirandaProtocol->QueryInterface( &pProtocol );
			ATLASSERT( SUCCEEDED( hr ) );

			CComPtr< IUrlAccessor > pAccessor;
			hr = pProtocol->CreateAccessor( GetCurrentUserURL() + pszCmdLine, NULL, NULL, NULL, &pAccessor );
			ATLASSERT( SUCCEEDED( hr ) );

			CComPtr< IFilter > pFilter;
			hr = pAccessor->BindToFilter( &pFilter );
			ATLASSERT( SUCCEEDED( hr ) );

			ULONG nFlags;
			hr = pFilter->Init( 0, 0, NULL, &nFlags );
			ATLASSERT( SUCCEEDED( hr ) );

			for ( ;; )
			{
				STAT_CHUNK pStat = {};
				hr = pFilter->GetChunk( &pStat );
				if ( hr == FILTER_E_END_OF_CHUNKS )
					break;
				ATLASSERT( SUCCEEDED( hr ) );

				CComHeapPtr< PROPVARIANT > sprop;
				hr = pFilter->GetValue( &sprop );
				ATLASSERT( SUCCEEDED( hr ) );
				PropVariantClear( sprop );
			}

			return hr;
		}
#endif*/ // _DEBUG
	}

	if ( bInstall )
	{	
		hr = DllRegisterServer();
		if ( FAILED( hr ) )
		{
			DllUnregisterServer();
		}
	}
	else
	{
		hr = DllUnregisterServer();
	}

	return hr;
}

CMirandaSearchModule::CMirandaSearchModule()
	: m_hCommandPipe( INVALID_HANDLE_VALUE )
{
}

CMirandaSearchModule::~CMirandaSearchModule()
{
	Close();
}

BOOL CMirandaSearchModule::DllMain(DWORD dwReason, LPVOID lpReserved)
{
	BOOL bSuccess = __super::DllMain( dwReason, lpReserved );
	if ( bSuccess && dwReason == DLL_PROCESS_ATTACH )
	{
		m_OSVersion.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
		GetVersionEx( &m_OSVersion );

		GetModuleFileName( _AtlBaseModule.GetModuleInstance(), m_sModule.GetBuffer( MAX_PATH ), MAX_PATH );
		m_sModule.ReleaseBuffer();

		DWORD handle = NULL;
		if ( DWORD size = GetFileVersionInfoSize( m_sModule, &handle ) )
		{
			if ( char* ver = (char*)GlobalAlloc( GPTR, size ) )
			{
				if ( GetFileVersionInfo( m_sModule, handle, size, ver ) )
				{
					UINT len = 0;
					VS_FIXEDFILEINFO* fix = NULL;
					if ( VerQueryValue( ver, _T("\\"), (void**)&fix, &len ) && len )
					{
						m_nVersion[ 0 ] = (BYTE)HIWORD( fix->dwFileVersionMS );
						m_nVersion[ 1 ] = (BYTE)LOWORD( fix->dwFileVersionMS );
						m_nVersion[ 2 ] = (BYTE)HIWORD( fix->dwFileVersionLS );
						m_nVersion[ 3 ] = (BYTE)LOWORD( fix->dwFileVersionLS );
					}

					LPCTSTR szFileDescription = NULL;
					if ( VerQueryValue( ver, _T("\\StringFileInfo\\000004b0\\FileDescription"), (void**)&szFileDescription, &len ) && len )
					{
						m_sFileDescription = szFileDescription;
					}

					LPCTSTR szProductName = NULL;
					if ( VerQueryValue( ver, _T("\\StringFileInfo\\000004b0\\ProductName"), (void**)&szProductName, &len ) && len )
					{
						m_sProductName = szProductName;
					}

					LPCTSTR szLegalCopyright = NULL;
					if ( VerQueryValue (ver, _T("\\StringFileInfo\\000004b0\\LegalCopyright"), (void**)&szLegalCopyright, &len ) && len )
					{
						m_sLegalCopyright = szLegalCopyright;
					}

					LPCTSTR szComments = NULL;
					if ( VerQueryValue (ver, _T("\\StringFileInfo\\000004b0\\Comments"), (void**)&szComments, &len ) && len )
					{
						m_sComments = szComments;
					}

					LPCTSTR szCompanyName = NULL;
					if ( VerQueryValue (ver, _T("\\StringFileInfo\\000004b0\\CompanyName"), (void**)&szCompanyName, &len ) && len )
					{
						m_sCompanyName = szCompanyName;
					}

					LPCTSTR szEMail = NULL;
					if ( VerQueryValue (ver, _T("\\StringFileInfo\\000004b0\\EMail"), (void**)&szEMail, &len ) && len )
					{
						m_sEMail = szEMail;
					}
				}
				GlobalFree( ver );
			}
		}

		pluginInfo.shortName = (LPSTR)(LPCSTR)m_sProductName;
		pluginInfo.version = PLUGIN_MAKE_VERSION( m_nVersion[ 0 ], m_nVersion[ 1 ], m_nVersion[ 2 ], m_nVersion[ 3 ] );
		pluginInfo.description = (LPSTR)(LPCSTR)m_sFileDescription;
		pluginInfo.author = (LPSTR)(LPCSTR)m_sCompanyName;
		pluginInfo.authorEmail = (LPSTR)(LPCSTR)m_sEMail;
		pluginInfo.copyright = (LPSTR)(LPCSTR)m_sLegalCopyright;
		pluginInfo.homepage = (LPSTR)(LPCSTR)m_sComments;
	}
	return bSuccess;
}

HRESULT CMirandaSearchModule::DllRegisterServer(BOOL bRegTypeLib)
{
	HRESULT hr = CAtlDllModuleT::DllRegisterServer( bRegTypeLib );
	if ( SUCCEEDED( hr ) )
	{
		hr = ManageURL( TRUE, TRUE, GetCurrentUserURL() );
	}
	return hr;
}

HRESULT CMirandaSearchModule::DllUnregisterServer(BOOL bUnRegTypeLib)
{
	ManageURL( FALSE, TRUE, GetCurrentUserURL() );

	return CAtlDllModuleT::DllUnregisterServer( bUnRegTypeLib );
}

void CMirandaSearchModule::RegisterClientPipe(HANDLE hPipe)
{
	CComCritSecLock< CComAutoCriticalSection > oLock( m_pSection );

	ATLASSERT( hPipe != INVALID_HANDLE_VALUE );

	if ( POSITION pos = m_hClientPipes.Find( hPipe ) )
		return;

	ATLTRACE( "RegisterClientPipe( 0x%08x )\n", hPipe ); 

	m_hClientPipes.AddTail( hPipe );
}

void CMirandaSearchModule::CloseClientPipe(HANDLE hPipe)
{
	CComCritSecLock< CComAutoCriticalSection > oLock( m_pSection );

	if ( POSITION pos = m_hClientPipes.Find( hPipe ) )
	{
		ATLASSERT( hPipe != INVALID_HANDLE_VALUE );
		ATLTRACE( "CloseClientPipe( 0x%08x )\n", hPipe ); 

		m_hClientPipes.RemoveAt( pos );

		DisconnectNamedPipe( hPipe );
		CloseHandle( hPipe );
	}
}

void CMirandaSearchModule::KickClientPipes()
{
	CComCritSecLock< CComAutoCriticalSection > oLock( m_pSection );

	for ( POSITION pos = m_hClientPipes.GetHeadPosition(); pos; )
	{
		HANDLE hPipe = m_hClientPipes.GetNext( pos );

		ATLASSERT( hPipe != INVALID_HANDLE_VALUE );
		ATLTRACE( "NotifyClientPipes() : Disconnect pipe: 0x%08x\n", hPipe ); 
		
		DisconnectNamedPipe( hPipe );
	}
}

void CMirandaSearchModule::Close()
{
	if ( m_hCommandPipe != INVALID_HANDLE_VALUE )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Close() : Pipe \"%hs\" closed\n", GetCurrentThreadId(), (LPCSTR)CT2A( COMMAND_CHANNEL ) );

		CloseHandle( m_hCommandPipe );
		m_hCommandPipe = INVALID_HANDLE_VALUE;
	}
}

BOOL CMirandaSearchModule::Open()
{
	if ( m_hCommandPipe != INVALID_HANDLE_VALUE )
	{
		return TRUE;
	}
		
	m_hCommandPipe = CreateFile( COMMAND_CHANNEL, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );
	BOOL bSuccess = ( m_hCommandPipe != INVALID_HANDLE_VALUE );
	DWORD dwError = GetLastError();
	if ( ! bSuccess )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Could not connect to pipe \"%hs\": 0x%08x\n", GetCurrentThreadId(), (LPCSTR)CT2A( COMMAND_CHANNEL ), dwError );
		Close();
		return FALSE;
	}

	if ( dwError != ERROR_SUCCESS )
	{
		bSuccess = WaitNamedPipe( COMMAND_CHANNEL, 5000 );
		dwError = GetLastError();
		if ( ! bSuccess )
		{
			ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Could not open pipe \"%hs\" in 5 seconds: %u\n", GetCurrentThreadId(), (LPCSTR)CT2A( COMMAND_CHANNEL ), dwError ); 
			Close();
			return FALSE;
		}
	}

	DWORD dwMode = PIPE_READMODE_MESSAGE | PIPE_WAIT;
	bSuccess = SetNamedPipeHandleState( m_hCommandPipe, &dwMode, NULL, NULL );
	dwError = GetLastError();
	if ( ! bSuccess ) 
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Could not set pipe \"%hs\" sate: %u\n", GetCurrentThreadId(), (LPCSTR)CT2A( COMMAND_CHANNEL ), dwError ); 
		Close();
		return FALSE;
	}

	CAtlArray< BYTE > pVersion;
	if ( ! Command( m_hCommandPipe, "VERSION?", pVersion ) || pVersion.GetCount() != sizeof( DWORD ) )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Could not check version\n", GetCurrentThreadId() ); 
		Close();
		return FALSE;
	}

	if ( m_nVersion[ 0 ] != pVersion[ 0 ] ||
		 m_nVersion[ 1 ] != pVersion[ 1 ] ||
		 m_nVersion[ 2 ] != pVersion[ 2 ] ||
		 m_nVersion[ 3 ] != pVersion[ 3 ] )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Invalid version: %u.%u.%u.%u\n", GetCurrentThreadId(), pVersion[ 0 ], pVersion[ 1 ], pVersion[ 2 ], pVersion[ 3 ] );
		Close();
		return FALSE;
	}

	CAtlArray< BYTE > pMe;
	if ( ! Command( m_hCommandPipe, "WHOAMI?", pMe ) || pMe.GetCount() < sizeof( WCHAR ) || ( pMe.GetCount() & 1 ) != 0 )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Could not get user name\n", GetCurrentThreadId() );
		Close();
		return FALSE;
	}

	m_sMe = CString( (LPCWSTR)pMe.GetData(), (int)( pMe.GetCount() / sizeof( WCHAR ) ) );
	m_sMe.Trim();
	if ( m_sMe.IsEmpty() || m_sMe.CompareNoCase( _T("'(Unknown Contact)'") ) == 0 )
	{
		DWORD nSize = 256;
		GetUserName( m_sMe.GetBuffer( 256 ), &nSize );
		m_sMe.ReleaseBuffer();
	}

	ATLTRACE( "0x%08x : CMirandaSearchModule::Open() : Successfully connected to pipe \"%hs\". User: \"%hs\"\n", GetCurrentThreadId(), (LPCSTR)CT2A( COMMAND_CHANNEL ), (LPCSTR)CT2A( m_sMe ) );

	return TRUE;
}

BOOL CMirandaSearchModule::ReadData(LPCSTR szURL, CAtlArray< BYTE >& pAnswer)
{
	CComCritSecLock< CComAutoCriticalSection > oLock( m_pSection );

	if ( ! Open() )
		return FALSE;

	if ( ! Command( m_hCommandPipe, szURL, pAnswer ) )
	{
		ATLTRACE( "0x%08x : CMirandaSearchModule::ReadData( \"%hs\" ) : Could not send command.\n", GetCurrentThreadId(), szURL ); 
		Close();
		return FALSE;
	}

	return TRUE;
}

CString GetCurrentUserURL()
{
	CString strURL;

	HRESULT hr = E_FAIL;
	CAccessToken tkn;
	if ( tkn.GetEffectiveToken( TOKEN_QUERY ) )
	{
 		CSid sid;
		try
		{
			if ( tkn.GetUser( &sid ) )
			{
				size_t cchSID = _tcslen( sid.Sid() ) + 1;
				CComHeapPtr< WCHAR > spszSID;
				hr = E_OUTOFMEMORY;
				if ( spszSID.Allocate( cchSID ) )
				{
					hr = StringCchCopy( spszSID, cchSID, sid.Sid() );
					if ( SUCCEEDED( hr ) )
					{
						strURL.Format( _T("%s{%s}/"), PROTOCOL, (LPCTSTR)spszSID );
					}
				}
			}
		}
		catch ( CAtlException &e )
		{
			hr = e.m_hr;
		}
	}
	return strURL;
}

URLLevel ParseURL(const CString& sURL, CString& sRelativeURL)
{
	// Test for current user
	if ( _tcsnicmp( PROTOCOL, sURL, _countof( PROTOCOL ) - 1 ) == 0 )
	{
		const CString sMyRoot = GetCurrentUserURL();
		if ( sURL.Left( sMyRoot.GetLength() ).CompareNoCase( sMyRoot ) == 0 )
		{
			sRelativeURL = sURL.Mid( sMyRoot.GetLength() - 1 );
			if ( sRelativeURL.IsEmpty() )
			{
				sRelativeURL = _T( "/" );	// Root Fix
				return URL_LEVEL_CONTACTS;
			}
			else if ( sRelativeURL.GetAt( 0 ) == _T( '/' ) )
			{
				if ( sRelativeURL.GetLength() == 1 )
					return URL_LEVEL_CONTACTS;
				else
				{
					const int nPos = sRelativeURL.Find( _T( '/' ), 1 );
					if ( nPos == -1 )
					{
						sRelativeURL += _T( "/" );	// Root Fix
						return URL_LEVEL_EVENTS;
					}
					else if ( nPos == sRelativeURL.GetLength() - 1 )
						return URL_LEVEL_EVENTS;
					else if ( sRelativeURL.Find( _T( '/' ), nPos + 1 ) == -1 )
						return URL_LEVEL_MESSAGE;
				}
			}
		}
		else if ( sURL.GetLength() == _countof( PROTOCOL ) - 1 ||
				( sURL.GetLength() == _countof( PROTOCOL ) && sURL.GetAt( _countof( PROTOCOL ) - 1 ) == _T('/') ) )
		{
			sRelativeURL = _T( "/" );
			return URL_LEVEL_ROOT;
		}
	}
	return URL_LEVEL_BAD;
}

HRESULT OnURLChanged(LPCWSTR szURL, SEARCH_KIND_OF_CHANGE nKind, BOOL bHighPriority)
{
	CComPtr< ISearchManager > pManager;
	HRESULT hr = pManager.CoCreateInstance( __uuidof( CSearchManager ) );
	if ( SUCCEEDED( hr ) )
	{
		CComPtr< ISearchCatalogManager > pCatalog;
		hr = pManager->GetCatalog( L"SystemIndex", &pCatalog );
		if ( SUCCEEDED( hr ) )
		{
			CComPtr< ISearchPersistentItemsChangedSink > pSink;
			hr = pCatalog->GetPersistentItemsChangedSink( &pSink );
			if ( SUCCEEDED( hr ) )
			{
				SEARCH_ITEM_PERSISTENT_CHANGE change =
				{
					(SEARCH_KIND_OF_CHANGE)( (int)nKind | ( ( szURL[ wcslen( szURL ) - 1 ] == _T('/') ) ? (int)SEARCH_CHANGE_SEMANTICS_DIRECTORY : 0 ) ),
					(LPWSTR)szURL,
					(LPWSTR)szURL,
					( bHighPriority ? SEARCH_HIGH_PRIORITY : SEARCH_NORMAL_PRIORITY )
				};
				HRESULT hr_result;
				hr = pSink->OnItemsChanged( 1, &change, &hr_result );
				if ( SUCCEEDED( hr ) )
				{
					hr = hr_result;
					ATLTRACE( "OnURLChanged() : Notified search root \"%hs\" [0x%08x]: 0x%08x\n", (LPCSTR)CW2A( szURL ), (int)change.Change, hr );
				}
				else
					ATLTRACE( "OnURLChanged() : OnItemsChanged() failed: 0x%08x\n", hr );
			}
			else
				ATLTRACE( "OnURLChanged() : ISearchPersistentItemsChangedSink is anavailable: 0x%08x\n", hr );
		}
		else
			ATLTRACE( "OnURLChanged() : SystemIndex is anavailable: 0x%08x\n", hr );
	}
	else
		ATLTRACE( "OnURLChanged() : ISearchManager is anavailable: 0x%08x\n", hr );
	return hr;
}

HRESULT ManageURL(BOOL bCreate, BOOL bClean, LPCWSTR szURL)
{
	CComPtr< ISearchManager > pManager;
	HRESULT hr = pManager.CoCreateInstance( __uuidof( CSearchManager ) );
	if ( SUCCEEDED( hr ) )
	{
		CComPtr< ISearchCatalogManager > pCatalog;
		hr = pManager->GetCatalog( L"SystemIndex", &pCatalog );
		if ( SUCCEEDED( hr ) )
		{ 
			CComPtr< ISearchCrawlScopeManager > pScope;
			hr = pCatalog->GetCrawlScopeManager( &pScope );
			if ( SUCCEEDED( hr ) )
			{
				BOOL bFoundRoot = FALSE;
				{
					CComPtr< IEnumSearchRoots > pRoots;
					HRESULT hr = pScope->EnumerateRoots( &pRoots );
					if ( hr == S_OK )
					{
						// Check if has this search root already
						for (;;)
						{
							CComPtr< ISearchRoot > pRoot;
							hr = pRoots->Next( 1, &pRoot, NULL );
							if ( hr != S_OK )
								break;

							CComHeapPtr< WCHAR > spszRootURL;
							hr = pRoot->get_RootURL( &spszRootURL );
							if ( SUCCEEDED( hr ) )
							{
								// Check if this is our url
								if ( _wcsicmp( szURL, spszRootURL ) == 0 )
								{
									// Found a dupe, hr is S_OK
									bFoundRoot = TRUE;
									ATLTRACE( "ManageURL() : Found search root \"%hs\"\n", (LPCSTR)CW2A( szURL ) );
									break;
								}
							}
						}
					}
				}

				if ( bCreate && bFoundRoot && ! bClean )
				{
					// Root found - do nothing
					hr = S_FALSE;
				}
				else if ( bCreate )
				{
					// Do an explicit remove to start with a clean registration
					if ( bFoundRoot )
					{
						hr = pScope->RemoveRoot( szURL );
						if ( SUCCEEDED( hr ) )
						{ 
							hr = pScope->SaveAll();

							ATLTRACE( "ManageURL() : Removed search root \"%hs\"\n", (LPCSTR)CW2A( szURL ) );
						}
					}

					// Need to add the root and default scope
					CComPtr< ISearchRoot > pRoot;
					hr = pRoot.CoCreateInstance( __uuidof( CSearchRoot ) );
					if ( SUCCEEDED( hr ) )
					{
						// Need this so that the WDS API's work even though the user is not an Admin
						hr = CoSetProxyBlanket( pRoot, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
							NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );
						if ( SUCCEEDED( hr ) )
						{
							hr = pRoot->put_RootURL( szURL );
							if ( SUCCEEDED( hr ) )
							{
								hr = pRoot->put_ProvidesNotifications( TRUE );
								if ( SUCCEEDED( hr ) )
								{
									hr = pRoot->put_IsHierarchical( TRUE );
									if ( SUCCEEDED( hr ) )
									{
										hr = pRoot->put_UseNotificationsOnly( FALSE );
										if ( SUCCEEDED( hr ) )
										{
											hr = pRoot->put_FollowDirectories( TRUE );
											if ( SUCCEEDED( hr ) )
											{
												hr = pScope->AddRoot( pRoot );
												if ( SUCCEEDED( hr ) )
												{
													hr = pScope->AddHierarchicalScope( szURL, TRUE, TRUE, FALSE );
													if ( SUCCEEDED( hr ) )
													{
														hr = pScope->SaveAll();

														ATLTRACE( "ManageURL() : Added search root \"%hs\"\n", (LPCSTR)CW2A( szURL ) );

														// Always re-index failed items of root
														CComQIPtr< ISearchCatalogManager2 > pCatalog2( pCatalog );
														if ( pCatalog2 )
															pCatalog2->PrioritizeMatchingURLs( CString( szURL ) + _T("*"),
																PRIORITIZE_FLAG_RETRYFAILEDITEMS | PRIORITIZE_FLAG_IGNOREFAILURECOUNT );
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
				else if ( bFoundRoot )
				{
					// Need to remove the search root
					hr = pScope->RemoveRoot( szURL );
					if ( SUCCEEDED( hr ) )
					{ 
						hr = pScope->SaveAll();

						ATLTRACE( "ManageURL() : Removed search root \"%hs\"\n", (LPCSTR)CW2A( szURL ) );
					}
				}
			}
		}
	}
	return hr;
}

BOOL Is64Windows()
{
#ifdef _WIN64
	return TRUE;
#else
	typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
	BOOL bIsWow64 = FALSE;
	if ( LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress( GetModuleHandle( _T("kernel32") ), "IsWow64Process" ) )
	{
		fnIsWow64Process( GetCurrentProcess(), &bIsWow64 );
	}
    return bIsWow64;
#endif
}
